using System;
using System.Collections.Generic;
using System.Text;
using Directory.Model;
using System.Security.Cryptography;
using System.Data;

namespace Directory.BLL
{
    public class User
    {
		private int maxInvalidPasswordAttempts = 10;
		private int passwordAttemptWindow = 10;

        private Directory.IDAL.IUser dal = new Directory.DAL.User();

		public UserInfo CreateUser(string username, string password, out UserInfo.ErrorCode ErrorCode)
		{
			return this.CreateUser(username, password, null, null, out ErrorCode);
		}

		public UserInfo CreateUser(string username, string password, string passwordQuestion, string passwordAnswer, out UserInfo.ErrorCode ErrorCode)
        {
			return this.CreateUser(username, username, password, passwordQuestion, passwordAnswer, true, false, out ErrorCode);
        }

		public UserInfo CreateUser(string username, string email, string password, string passwordQuestion, string passwordAnswer, bool isApproved, bool isAnonymous, out UserInfo.ErrorCode ErrorCode)
        {
			if (!BLLHelper.ValidateUserName(ref username))
			{
				ErrorCode = UserInfo.ErrorCode.InvalidUserName;
				return null;
			}
			if (!BLLHelper.ValidatePassword(ref password))
            {
				ErrorCode = UserInfo.ErrorCode.InvalidPassword;
                return null;
            }

            string salt = GenerateSalt();
            string newPassword = EncodePassword(password, salt);

			return dal.CreateUser(username, email, newPassword, salt, passwordQuestion, passwordAnswer, isApproved, isAnonymous, true, out ErrorCode);
        }

        public void DeleteUser(string username)
        {
			if (BLLHelper.ValidateUserName(ref username))
			{
				dal.DeleteUser(username);
			}
			else
			{
				throw new ArgumentException("Invalid username parameter.");
			}
        }

		public void UpdateUser(UserInfo user, out UserInfo.ErrorCode ErrorCode)
        {
			dal.UpdateUser(user, true, out ErrorCode);
        }

		public UserInfo GetUserInfo(string username)
		{
			if (BLLHelper.ValidateUserName(ref username))
			{
				return dal.GetUserInfo(username);
			}
			else
			{
				throw new ArgumentException("Invalid username parameter.");
				//return null;
			}
		}

        public bool ValidateUser(string username,string password)
        {
			if (!BLLHelper.ValidateUserName(ref username))
			{
				return false;
			}
            if (CheckPassword(username, password))
            {
                dal.UpdateUserLastLoginTime(username);
                return true;
            }
            return false;
        }

		public bool ValidatePassword(string username, string password)
		{
			if (!BLLHelper.ValidateUserName(ref username))
			{
				return false;
			}
			string salt = null;
			string pwd = null;
			if (dal.GetPassword(username, out pwd, out salt, maxInvalidPasswordAttempts, passwordAttemptWindow))
			{
				if (pwd.CompareTo(EncodePassword(password, salt)) == 0)
				{
					return true;
				}
			}
			return false;
		}

		public void UpdateUserLockOutSate(string username, bool isLocked)
		{
			if (BLLHelper.ValidateUserName(ref username))
			{
				dal.UpdateUserLockOutSate(username, isLocked);
			}
			else
			{
				throw new ArgumentException("Invalid username parameter.");
			}
		}

		public void ResetPassword(string username, string password)
		{
			if (BLLHelper.ValidateUserName(ref username) && BLLHelper.ValidatePassword(ref password))
			{
				string salt = GenerateSalt();
				string newPassword = EncodePassword(password, salt);
				dal.ResetPassword(username, newPassword, salt);
			}
			else
			{
				throw new ArgumentException("Invalid username parameter or password parameter.");
			}
		}

		public string GetPasswordQuestion(string username)
		{
			if (BLLHelper.ValidateUserName(ref username))
			{
				return dal.GetPasswordQuestion(username);
			}
			else
			{
				return null;
			}
		}

		public bool ValidatePasswordAnswer(string username, string passwordQuestion, string passwordAnswer, int maxInvalidPasswordAnswerAttempts, int passwordAnswerAttemptWindow)
		{
			if (BLLHelper.ValidateUserName(ref username) && BLLHelper.ValidateNotEmptyStringParameter(ref passwordQuestion) && BLLHelper.ValidateNotEmptyStringParameter(ref passwordAnswer))
			{
				return dal.ValidatePasswordAnswer(username, passwordQuestion, passwordAnswer,maxInvalidPasswordAnswerAttempts,passwordAnswerAttemptWindow);
			}
			return false;
		}

        #region PrivateMethods

        private string GenerateSalt()
        {
            byte[] data = new byte[0x10];
            new RNGCryptoServiceProvider().GetBytes(data);
            return Convert.ToBase64String(data);
        }

        private string EncodePassword(string password, string salt)
        {
            byte[] bytes = Encoding.Unicode.GetBytes(password);
            byte[] src = Convert.FromBase64String(salt);
            byte[] dst = new byte[src.Length + bytes.Length];
            byte[] inArray = null;
            Buffer.BlockCopy(src, 0, dst, 0, src.Length);
            Buffer.BlockCopy(bytes, 0, dst, src.Length, bytes.Length);

            HashAlgorithm algorithm = HashAlgorithm.Create("MD5");

            inArray = algorithm.ComputeHash(dst);

            return Convert.ToBase64String(inArray);
        }

        private bool CheckPassword(string username, string password)
        {
            string salt = null;
            string pwd=null;
			if (dal.GetPassword(username, out pwd, out salt, maxInvalidPasswordAttempts, passwordAttemptWindow))
			{
				if (pwd.CompareTo(EncodePassword(password, salt)) == 0)
				{
					dal.UpdateUser(username, true);
					return true;
				}
				else
				{
					dal.UpdateUser(username, false);
				}
				return false;
			}
			else
			{
				// too much times to attempt to check password;
				return false;
			}
        }

        #endregion
    }
}
